{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "179685a3",
   "metadata": {},
   "source": [
    "# Report 3 Sudoku"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c42dbd84",
   "metadata": {},
   "source": [
    "1.任务目标"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ada4aad7",
   "metadata": {},
   "source": [
    "构建CNN卷积神经网络，通过对数独数据集的训练来解决数独问题，需要根据9×9盘面上的已知数字，推理出所有剩余空格的数字，并满足每一行、每一列、每一个粗线宫（3*3）内的数字。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "716c6d19",
   "metadata": {},
   "source": [
    "2.基础知识"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8030a4fd",
   "metadata": {},
   "source": [
    "1.“数独”抽象来看是一个求解空间特征的问题，9*9盘可以转化为9*9像素图片，而卷积神经网络擅长求解空间特征。所以我们就可以用它来解决“数独问题”。\n",
    "2.“数独”问题是希望机器学到在不止一个空白格子上填写数字，属于多标签模型的应用。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3e06862b",
   "metadata": {},
   "source": [
    "3.背景"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "11ad83bd",
   "metadata": {},
   "source": [
    "数独（shù dú, Sudoku）是源自18世纪瑞士的一种数学游戏。是一种运用纸、笔进行演算的逻辑游戏。玩家需要根据9×9盘面上的已知数字，推理出所有剩余空格的数字，并满足每一行、每一列、每一个粗线宫（3*3）内的数字均含1-9，不重复。数独盘面是个九宫，每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件，利用逻辑和推理，在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次，所以又称“九宫格”。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "192008cf",
   "metadata": {},
   "source": [
    "4.原理"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e55fe2a",
   "metadata": {},
   "source": [
    "1.准备数据集，在https://www.kaggle.com/bryanpark/sudoku 网站中下载含有一百万数独的数据集。数据集中第一列为问题，第二列为该问题的答案。2.数据预处理，将问题列的81个数字转化为9*9的二维矩阵，给卷积网络输入一个9*9的表格，并让其输出9*9的表格。然后对表格中的数字均进行除以9再减去0.5这一算式，让数据变为以0均值为中心并且范围在（-0.5，0.5）之间，神经网络通常在以零为中心的归一化数据时表现更优。3.设计神经网络，在模型训练中让其输出尺寸为81*9，81对应的是81个格子，9则对应数字1-9，通过模型的训练来分析每个格子最大概率为1-9中的哪个数字。4.进行训练，并用数独游戏的例子来测试模型。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "976644c8",
   "metadata": {},
   "source": [
    "5.实验"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dff87678",
   "metadata": {},
   "outputs": [],
   "source": [
    "import copy\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "from sklearn.model_selection import train_test_split\n",
    "import keras\n",
    "from keras.layers import Activation\n",
    "from keras.layers import Conv2D, BatchNormalization, Dense, Flatten, Reshape#导入相关库\n",
    "\n",
    "def get_data(file): #将数据集导入\n",
    "    data = pd.read_csv(file)\n",
    "\n",
    "    feat_raw = data['quizzes']\n",
    "    label_raw = data['solutions']\n",
    "\n",
    "    feat = []\n",
    "    label = []\n",
    "\n",
    "    for i in feat_raw:\n",
    "        x = np.array([int(j) for j in i]).reshape((9, 9, 1))\n",
    "        feat.append(x)\n",
    "\n",
    "    feat = np.array(feat)\n",
    "    feat = feat / 9\n",
    "    feat -= .5\n",
    "\n",
    "    for i in label_raw:\n",
    "        x = np.array([int(j) for j in i]).reshape((81, 1)) - 1\n",
    "        label.append(x)\n",
    "\n",
    "    label = np.array(label)\n",
    "\n",
    "    del (feat_raw)\n",
    "    del (label_raw)\n",
    "\n",
    "    x_train, x_test, y_train, y_test = train_test_split(feat, label, test_size=0.2, random_state=42)\n",
    "\n",
    "    return x_train, x_test, y_train, y_test\n",
    "\n",
    "\n",
    "\n",
    "def get_model():#设计卷积神经网络，建立模型\n",
    "    model = keras.models.Sequential()\n",
    "\n",
    "    model.add(Conv2D(64, kernel_size=(3, 3), activation='relu', padding='same', input_shape=(9, 9, 1)))\n",
    "    model.add(BatchNormalization())\n",
    "    model.add(Conv2D(64, kernel_size=(3, 3), activation='relu', padding='same'))\n",
    "    model.add(BatchNormalization())\n",
    "    model.add(Conv2D(128, kernel_size=(1, 1), activation='relu', padding='same'))\n",
    "\n",
    "    model.add(Flatten())\n",
    "    model.add(Dense(81 * 9))\n",
    "    model.add(Reshape((-1, 9)))\n",
    "    model.add(Activation('softmax'))\n",
    "\n",
    "    return model\n",
    "\n",
    "x_train, x_test, y_train, y_test = get_data(r'C:\\Users\\Junjie Wang\\Desktop\\机器学习\\sudoku.csv')#利用定义的函数下载模型\n",
    "model = get_model()#开始训练模型\n",
    "\n",
    "adam = keras.optimizers.Adam(lr=.001)\n",
    "model.compile(loss='sparse_categorical_crossentropy', optimizer=adam)\n",
    "\n",
    "model.fit(x_train, y_train, batch_size=32, epochs=2)\n",
    "\n",
    "\n",
    "def norm(a):#让数据变为以0均值为中心并且范围在（-0.5，0.5）之间\n",
    "    return (a / 9) - .5\n",
    "\n",
    "\n",
    "def denorm(a):#将（-0.5，0.5）之间返回之前的值\n",
    "    return (a + .5) * 9\n",
    "\n",
    "\n",
    "def inference_sudoku(sample):#定义函数，对矩阵中空白部分一个一个进行填充\n",
    "    \n",
    "\n",
    "    feat = copy.copy(sample)\n",
    "\n",
    "    while (1):\n",
    "\n",
    "        out = model.predict(feat.reshape((1, 9, 9, 1)))\n",
    "        out = out.squeeze()\n",
    "\n",
    "        pred = np.argmax(out, axis=1).reshape((9, 9)) + 1\n",
    "        prob = np.around(np.max(out, axis=1).reshape((9, 9)), 2)\n",
    "\n",
    "        feat = denorm(feat).reshape((9, 9))\n",
    "        mask = (feat == 0)\n",
    "\n",
    "        if (mask.sum() == 0):\n",
    "            break\n",
    "\n",
    "        prob_new = prob * mask\n",
    "\n",
    "        ind = np.argmax(prob_new)\n",
    "        x, y = (ind // 9), (ind % 9)\n",
    "\n",
    "        val = pred[x][y]\n",
    "        feat[x][y] = val\n",
    "        feat = norm(feat)\n",
    "\n",
    "    return pred\n",
    "\n",
    "\n",
    "def test_accuracy(feats, labels):#测试\n",
    "    correct = 0\n",
    "\n",
    "    for i, feat in enumerate(feats):\n",
    "\n",
    "        pred = inference_sudoku(feat)\n",
    "\n",
    "        true = labels[i].reshape((9, 9)) + 1\n",
    "\n",
    "        if (abs(true - pred).sum() == 0):\n",
    "            correct += 1\n",
    "\n",
    "    print(correct / feats.shape[0])\n",
    "test_accuracy(x_test[:100], y_test[:100])\n",
    "\n",
    "\n",
    "def solve_sudoku(game):#自己输入game进行测试\n",
    "    game = game.replace('\\n', '')\n",
    "    game = game.replace(' ', '')\n",
    "    game = np.array([int(j) for j in game]).reshape((9, 9, 1))\n",
    "    game = norm(game)\n",
    "    game = inference_sudoku(game)\n",
    "    return game\n",
    "game = '''\n",
    "          0 8 0 0 3 2 0 0 1\n",
    "          7 0 3 0 8 0 0 0 2\n",
    "          5 0 0 0 0 7 0 3 0\n",
    "          0 5 0 0 0 1 9 7 0\n",
    "          6 0 0 7 0 9 0 0 8\n",
    "          0 4 7 2 0 0 0 5 0\n",
    "          0 2 0 6 0 0 0 0 9\n",
    "          8 0 0 0 9 0 3 0 5\n",
    "          3 0 0 8 2 0 0 1 0\n",
    "      '''\n",
    "\n",
    "game = solve_sudoku(game)\n",
    "\n",
    "print('solved puzzle:\\n')\n",
    "print(game)\n",
    "np.sum(game, axis=1)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5bac3779",
   "metadata": {},
   "source": [
    "6.总结"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bf5601de",
   "metadata": {},
   "source": [
    "1.对于使用卷积神经网络解决“数独”问题，可以将数独的9*9盘转化为9*9的矩阵作为输入，让卷积神经网络对其进行训练。2.神经网络通常在以零为中心的归一化数据时表现更优，可以将训练集中的1-9均除以9再减去0.5，让数据变为以0均值为中心并且范围在（-0.5，0.5）之间。3.在运行代码时遇到fixture ... not found”问题，原因是因为代码中包含了带有test的函数，pycharm自动运行使用的是pytest。将运行方式改为python即可解决问题。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
